all About the minilisp application

Ruben Kleiman


Minor editing by slm 3/10/92.


If you're really interested in the nitty-gritty 
details of developing a program using MCL, 
this is your opportunity to learn by doing. 
We'll describe here how to build key features 
of the sample program Mini-Application. The 
complete source code for this program is also 
contained on this disc.

Our sample application combines some of the features 
of programs like MacDraw and HyperCard. Its main 
components are:

¥ Windows in which the user can build 
graphics

¥ Palettes from which tools can be selected 
and from which graphic objects can be 
dragged out

¥ A variety of graphic objects (for example, 
buttons, fields) that can be dragged from 
the palettes to build something in the 
windows

¥ Menus to simplify the user interface

¥ An event system that approximates 
HyperCardÕs, including the ability to write 
scripts for objects - albeit in Lisp

You may want to run the application before learning 
how it was built.  Instructions follow in "To Run the 
Application."  Starting with the section "Creating the 
Framework," we show how to build the application in a 
top-down manner, paying attention to the features of 
the development environment and of Lisp that enhance 
programmer productivity (and enjoyment). We craft the 
key methods or functions for each aspect of the 
application. We don't provide a complete exegesis of 
the application, but  touch on every component 
mentioned above, implementing the main code for that 
feature. 

to run the application

To run the application, you need a copy of Macintosh 
Common Lisp, Version 2.0, available from 
APDA. MCL runs in 4 megabytes.

To run the application, open the file Read Me First.lisp 
into a text window in MCL and read the instructions. 
All you have to do is to set up the name of the 
directory where you installed the Mini-Application 
folder supplied on the CD ROM and evaluate the text 
window.  This will cause the application to load and 
run.

The application consists of the following files:

File Name				Description

Read Me First.lisp			The code that loads and runs 		
				        the application. It also initializes 		
				        objects needed by each run.

mini-application-loader.lisp		Loads the application code.

compile-mini-application.lisp		A file which, when loaded,
 					recompiles the application

globals.lisp				Declares all globals used in the
 					application.

draw-dialog-class.lisp			The class of windows and all
 					their methods.

palette-class.lisp			The class of palettes and all 		
				        their methods.

draw-item-class.lisp			The classes of all graphic
 					objects and all their methods.

menus.lisp				Defines the menus and menu 		
				        items used by this application, 			
			                as well as the functions they 				
		                        invoke.

default-handlers.lisp			Defines the default behavior of
 					the HyperCard-like script
 				        handlers, like MouseDown.

utilities.lisp				Utility functions used 			
				        throughout.

resources				Contains a few resources (for
					example, icons).


Creating The Framework

As we begin to write our application, we open a MCL 
text document in which to enter and save the code. To 
do this, go to the File menu and choose New. A 
document called New will be opened. 

We will run our application by calling the function 
start-application, which will bring up the necessary 
menus and display a greeting. Type the following into 
our empty document:

(defun start-application ()
  (when (welcome-accepted?) ; Display welcome message
    (show-menus)            ; Initialize the menubar
    (print ÒWelcome!Ó)))    ; Print greeting

You can begin each line with a tab, and the editor will 
automatically indent your code to the appropriate 
level. The defun function is how you define a function. 
This consists of a function name (first argument), a 
list that specifies the arguments assumed by the 
function (second argument, also known as the Òlambda 
listÓ), and any number of Lisp expressions (rest of 
arguments, also known as the body). Evaluating the 
function returns the value of the last expression in the 
body. In start-application, the lambda list has nothing 
in it, so the function does not take any arguments. The 
body consists of a conditional expression that calls 
the function welcome-accepted? and calls show-
menus if that function returns a value other than NIL. 
All lines beginning with a semicolon are comments.

To compile start-application, select the definition 
with the mouse and choose Eval Selection from the 
Eval menu. The compiler will print out a warning in the 
Listener to the effect that welcome-accepted? and 
show-menus are undefined. Lisp was tolerant:  it 
could have given you an error!  DonÕt worry about this: 
when you define those functions, everything will be ok. 
Now save your text by choosing Save As... from the File 
menu.

Our welcome dialog will display a string identifying 
the application and give the user an opportunity to 
cancel. This is straightforward with the predefined 
function y-or-n-dialog. Enter the following definition 
into your text window:

(defun welcome-accepted? ()
  (y-or-n-dialog "Welcome To My App. Ready, Set, Go?"
                 :yes-text  "Go Dude!"
                 :no-text   "Nope"))

y-or-n-dialog accepts as its first argument our 
message, and optional keyword arguments called :yes-
text and :no-text are assigned the values ÒGo Dude!Ó 
and "Nope," respectively. The latter are the text 
assigned to buttons on the welcome dialog. Since the 
call to y-or-n-dialog does not depend on any 
variables, we can immediately test welcome-
accepted? by selecting the text containing the call to 
y-or-n-dialog and then choosing Eval Selection from 
the Eval menu. The dialog should be shown. If you 
select the Go Dude! button, the function should return T 
(LispÕs Boolean for true). When you evaluate any 
expression either in the Listener or in a text window, 
the result of the evaluation is returned in the Listener. 
Select the Go Dude! button to see whatÕs returned.

Creating Our Menus

The function show-menus is invoked by start-
application when the welcome message is accepted. In 
our application, show-menus will eliminate the 
normal MCL menus and display a menu bar with the 
following menus:  the standard File, Edit, and Window 
menus, an Options menu, and a menu that provides 
information about the currently selected graphic 
object. For purposes of this demonstration, we will 
limit ourselves to building the File menu. Our File 
menu will have New, Close, and Quit menu items (we 
wonÕt support Save). See the source code for the 
definitions of the Close and Quit menu items.

Type and evaluate this expression in a text window:

(defvar *mini-application-file-menu*
  (make-instance 'menu :menu-title "File"))

First, defvar is the Lisp function that lets you define 
and initialize a global variable:  we have thus 
initialized the variable *mini-application-file-menu* 
to contain the menu instance. The method make-
instance, which is similar to New in SmallTalk and in 
MacApp, enables you to create an instance from a 
class, and to optionally initialize some of its slots. 
(The term slot is synonymous with instance variable in 
other object-based languages.) The first argument to 
make-instance is the name of the class, and the 
subsequent arguments are keyword-value pairs with 
optional initializations that depend on the class. 
Above, the predefined MCL class menu is instantiated 
and the :menu-title keyword option is initialized with 
the name of the menu. (The name menu is preceded 
with a single quotation mark to indicate that we are 
referring to the symbol menu rather than to the 
variable menu.)  

To test this instance, you can invoke the menu-install 
method on it:

(menu-install *mini-application-file-menu*)

The menu bar should now include a new File menu on 
the far right. Clicking on the new File menu will not 
yield any menu items, so letÕs define one. Evaluate the 
following:

(defvar *file-new-menu-item*
  (make-instance 'menu-item
    :menu-item-title "New"
    :command-key #\N
    :menu-item-action 'new-menu-item-action))

The predefined MCL class menu-item supports, 
among other things, an optional command-key 
character - characters in Lisp are represented by 
prefixing the character with #\ - and a menu item 
action. The latter is a function that will be called 
when the menu item is chosen. In this example, we 
define the keystroke combination Command-N to be 
equivalent to choosing the menu item. We have also 
given new-menu-item-action as the name of the 
function that should be called when this menu item is 
chosen - new-menu-item-action is defined in the 
section ÒCreating Our WindowÓ. The additional 
keywords :disabled, :menu-item-color, :menu-item-
checked, and :style permit you to specify whether the 
menu item should be disabled, the colors of the parts 
of the menu (such as background, text), whether the 
item should be checked, and its style.

We are ready to install the new menu item on the File 
menu. Type and evaluate the following in a text 
window:

(add-menu-items *mini-application-file-menu*
                *file-new-menu-item*)

add-menu-items is a predefined MCL method for 
menus that enables us to add one or more menu items 
to a menuÑwhether the menu is installed or not. If you 
pull down the new File menu, you should see the New 
item. However, if you choose this item, you should see 
an error report in the Listener telling you that new-
menu-item-action is undefined. Lisp is forgiving but 
not clairvoyant!

Now that we know how to define menus and menu 
items, we want to define a menu bar so that we can 
treat our applicationÕs menus as a unit. First, letÕs 
deinstall our File menu by evaluating the following:

(menu-deinstall *mini-application-file-menu*)

A menu bar consists of a list of menus that will be 
displayed in a left-to-right order. The complete source 
code for this application defines all menus and menu 
items, but here we will restrict ourselves to the File 
and Windows menus. We define the menu bar by 
evaluating this expression:

(defvar *mini-application-menubar*
  (list *mini-application-file-menu*
        *windows-menu*)))

The convenient variable *windows-menu* is bound by 
MCL to the menu that MCL already uses in its menu 
bar, so we add it here to get that functionality for 
free. To install our menu bar, evaluate this:

(set-menubar *mini-application-menubar*)

set-menubar deinstalls the MCL menubar and 
installs ours in its place. MCL provides a set of 
methods that can be invoked at any time on menus or 
menu items. For example, examine the New item after 
evaluating each of  the following lines:

(set-menu-item-check-mark *file-new-menu-item* t) ; Put a check mark

(set-menu-item-style *file-new-menu-item* :outline) ; Outline font

(set-menu-item-title *file-new-menu-item* "Was New") ; Other title

(menu-item-disable *file-new-menu-item*)  ; Disable the menu item

MCL binds its own menu bar to the global *default-
menubar* (never leave home without it!), so to get 
back to MCLÕs menu bar, evaluate:

(set-menubar *default-menubar*)

Finally, we want to define the function show-menus, 
which will be called by start-application when our 
application needs to be started. This is simple:

(defun show-menus ()
  (set-menubar *mini-application-menubar*))

The complete source code defines *mini-application-
menubar* to include all of the necessary menus and 
menu items to run the application.

Creating Our Window

To see how windows are handled, letÕs start right off 
by creating one. Evaluate:

(setq my-window (make-instance 'window))

You should immediately see a window named Untitled 
appear on your main screen . LetÕs see what we can do 
with windows. Examine the window after evaluating 
each of the following lines:

(set-window-title my-window "d e v e l o p") ; Change window title

(set-view-size my-window 200 200) ; Change window size

(set-view-position my-window 150 50) ; Change window position

set-window-title, set-view-size, and other functions 
used here and in other places are actually methods:  
they are sensitive to the types of objects being passed 
to them as arguments. As with other object-based 
languages, Lisp methods decide what to do based on the 
class of which the object is an instance. Unlike other 
object-based systems that only look at the class of 
the first argument, the Common Lisp Object System 
(CLOS) considers the classes of all of its arguments 
before deciding what to do. It is arguable just how 
useful this really is, but if you ever need this 
capability, itÕs there! CLOS is implemented such that 
you wonÕt pay for this feature unless you use it. 
Methods are defined using the function defmethod, 
while regular functions are defined using defun.

In our section on menus and in this section, weÕve seen 
that MCL uses the CLOS system to implement its user 
interface with a variety of methods Ñ for example, add-
menu-items, set-view-position. Fortunately, these 
methods are also available to us, the users. One other 
benefit of the MCL user interface is its view 
architecture and, in addition to windows, a useful 
variety of predefined views like radio buttons, check 
boxes, and editable text.  LetÕs understand MCLÕs view 
system so that we can exploit it for our application.

The view architecture allows one to define graphic 
objects as views. A view has its own coordinate 
system and origin relative to a view that contains it 
(called the view container); it is defined as a 
rectangular area determined by its position relative to 
its container and its size. It is thus easy to, for 
example, independently scroll separate views. A view 
can be a window, a radio button, or anything that can 
be displayed on the screen.

We will build our application windows with the help of 
the view system. However, we will retain some control 
over how views are displayed. For example, we want to 
control the back-to-front ordering of graphic objects 
in a window, to be able to move objects among 
windows, and to constrain the behavior of objects 
(such as tools) in palettes.

LetÕs create a subclass of the predefined window 
class that is customized for our application. This class 
will include a slot my-items that will hold a list of 
all the graphic objects that we draw into the window, 
remembering their back-to-front ordering. We start by 
creating a subclass of the window class named draw-
dialog. defclass is the CLOS function for defining a 
class:

(defclass draw-dialog (window)
  ((my-items :initform NIL)                ; List of all items in window
   (item-last-under-mouse :initform NIL)   ; Item currently under the mouse
   (browse-mode :initform nil)             ; Mode in which window is being used (default = author)
   (selections :initform nil)              ; Currently selected item(s)
   (create-by-rectangle :initform nil)     ; Can draw-items be created by dragging out a rectangle?
   )
  (:documentation "This class defines our windows"))

Our class draw-dialog adds five slots named my-items,
browse-mode, item-last-under-mouse, selections and
create-by-rectangle to its superclass, window. The first
argument to defclass is the name of the class, draw-dialog, 
followed by a list with the names of all classes, if 
any, from which we want to inherit (that is, that we 
want our class to be a subclass of)Ñin our case, just 
the window class. Multiple inheritance is supported 
and although things are, by default, inherited in the 
order in which they appear in this list, protocols are 
available in CLOS for controlling this. The third and 
fourth arguments shown here are a list of descriptors 
for each new slot that we want to define and 
documentation text, respectively.

A descriptor is a list that starts with the name of the 
slot followed by a set of keyword-value pairs that 
represent options concerning how the slots get 
initialized when an  instance of this class is created. 
We used the :initform keyword followed by the 
expression NIL. This means that when an instance of 
draw-dialog is created, the my-items slot will by 
default be set to whatever the following expression 
evaluates toÑthat is, NIL. 

The draw-dialog class will inherit slots from the 
window class and from the classes it inherits from. 
Verify that this new subclass definition works by 
creating an instance of it:

(setq my-window (make-instance 'draw-dialog))

One way to check whether our new slots my-items, 
browse-mode, item-last-under-mouse have, selections and
create-by-rectangle been added and initialized to the 
value NIL, is to get their slot values directly. These 
values can be obtained by evaluating the following 
expressions:

(slot-value my-window 'my-items)  ; Returns value of slot my-items
(slot-value my-window 'browse-mode)
(slot-value my-window 'item-last-under-mouse)
(slot-value my-window 'selections)
(slot-value my-window 'create-by-rectangle)

Another way to check these slots is by using the Lisp 
inspector to view the object instance. The inspector 
provides us with a description of any object (this 
includes constants, variables, and functions) that we 
want to look at. (In addition, as of this writing, the 
released version of the MCL 2.0 inspector will allow 
you to directly edit values.) To inspect the instance in 
my-window, evaluate (inspect my-window), which 
brings up a screen that looks like Figure 1.

Figure 1: [ See Figure 3 in develop, p104 ]

Viewing an Instance of the draw-dialog Class in the 
Inspector

The items following Local slots: are slots of the 
object bound to my-window. The slots that we added 
should be at the top, as shown; the remaining slots 
have been inherited from the window class. For 
example, the slot wptr contains the pointer to the 
Macintosh window definition block, and view-size is 
the point (that is, a long used as a point) for the 
windowÕs size. You should resist the temptation to 
directly access these slots, unless you absolutely need 
to. The predefined window methods, like set-view-
size, should be used instead. If youÕd like to inspect 
the window block itself, you can double-click on the 
line in Figure 1 with the wptr to get the display shown 
in Figure 2.

Figure 2: [ See Figure 4 in develop, p105 ]

Viewing the Window Record for Our Instance of 
draw-dialog in the Inspector

You could continue this process indefinitelyÑfor 
example, looking next at the rectangle records in the 
window record. As you look at these lower-level 
structures, you begin to see that although Lisp is a 
very high-level language, you can still access the 
system as if writing in assembler language. As we 
shall see later, this clarifies toolbox access 
considerably when compared with C and Pascal.

Now, we want to define the behavior of our 
windowÑwhat happens when it is created, when it is 
closed, when things are added to it, when the mouse is 
clicked on it, and so on. Fortunately, much of this 
behavior is already handled for us by the predefined 
window methods. 

In our menu section above, we defined for the File 
menu the New menu item which, when chosen, called 
the function new-menu-item-action. We are now 
ready to define this function, which creates an 
instance of a draw-dialog window:

(defvar *window-count* 0)   ; For a new windowÕs title

(defun new-menu-item-action ()
  (make-instance 'draw-dialog
    :window-title
    (format nil "Draw Dialog ~a" (incf *window-count*))
    :view-size (make-point (- *screen-width* 150)
                           (- *screen-height* 100))
    :view-position (make-point 5 40)
    :color-p *color-available*
    :window-type :document-with-zoom))

We used the Lisp format statement to create the 
window title. format is a sophisticated instrument 
not only for writing strings to any stream, but also for 
generating string expressions of arbitrary complexity. 
We use the :window-type option to require a 
document with zoom box. *screen-width* and 
*screen-height* are the main screenÕs width and 
height, respectively, which are supplied by MCL. 
Finally, incf is a macro that lets us increment and set 
the value of *window-count*.

One interesting behavior that we want to add to draw-
dialog is to make the window handle events like 
HyperCard does. The concept is that an instance of 
draw-dialog, as well as any object in it, can be 
scripted. A script will here be defined as a set of 
handlers. Instead of having a scripting language like 
HyperTalk, weÕll use Lisp, which is handier. A handler 
will be a method whose name is the name of the 
handler. The following types of handlers will therefore 
be defined:

idle			Periodically called by the system.

mouse-enter	        Called whenever the mouse enters the
			bounds of the object.

mouse-leave	        Called whenever the mouse leaves the
 			bounds of the object.

mouse-within	        Periodically called while the mouse is 
			within the bounds of the object.

key			Called whenever the object is selected 
			and a key is pressed.

mouse-down	        Called whenever the mouse goes down 
			within the bounds of the object.

mouse-up		Called whenever the mouse goes up 
			within the bounds of the object.

All objects will have default handlers that do nothing, 
except for the windowÕs idle handler, which simply 
passes the idle event to objects in its window.

WeÕll define a set of default handlers that will be 
called if the user does not write any scripts for these 
handlers. Most of these will do nothing. Here, we 
implement the idle default handler for the draw-
dialog class:

(defmethod idle ((this-window draw-dialog))
  (dolist (item (slot-value this-window 'my-items))
    (idle item)))

This method simply sends an idle message to each item 
in the my-item slot of the window. (Note how easily 
we have been able not only to talk about but also to 
show how we can control the items in our drag-dialog 
windows without having defined the objects 
themselves! This will be more dramatically clear as 
we proceed to provide the event-handling machinery 
that drives all of the events above.)

This is a simple method definition. Its name is idle and 
it has only one formal argument: an instance of a 
draw-dialog class. During the execution of the 
method, this instance is bound to the local variable 
this-window. In many other object-based languages, 
this variable is called self, but in Common Lisp it must 
be explicitly bound to a variable in the formal 
argument list. Why? Because, as we mentioned earlier, 
a Common Lisp method will look at the class of every 
argument passed to it before deciding which function 
to call. Thus, there isnÕt any distinguished self, but 
rather many selves competing for identityhood!

Another thing to notice in idle is how a slot of the 
object is accessed. The construct

(slot-value <object> <slot-name>)

is the default way to refer to an objectÕs slot value. 
However, the :accessor option of defclass enables you 
to define how you want to name the slot accessor. For 
example:

(defclass person
  ((age :initform 20
        :accessor age)))

enables you to access the age slot of an instance x of 
person via the expression (age x). Named accessors 
have the virtue of hiding the object system and are 
encouraged. However, we will consistently use slot-
value to access slot values in order to clarify our use 
of object slots in our tutorial.

Next, we want to drive the event system. From where? 
Unlike in other Macintosh programming environments, 
when we write a Lisp program we are not responsible 
for calling WaitNextEvent. Fortunately, the MCL 
window class has a predefined method called 
window-null-event-handler that is periodically 
called by MCL; in particular, after each 
WaitNextEvent is processed. MCL will apply this 
method to any active instance of a window class of 
which it is aware. This includes all instances of 
draw-dialog, since the latter is a subclass of 
window, giving us one of the hooks we needed. LetÕs 
define window-null-event-handler for draw-dialog 
to handle the event traffic as follows:

(defmethod window-null-event-handler ((w draw-dialog))
  (let* ((where (view-mouse-position w))   ; Window coordinate point of mouse
         (item (find-view-containing-point w (point-h where) (point-v where)))
         (last-under-mouse (slot-value w 'item-last-under-mouse)))
    ;; Handle mouse-within, mouse-enter and mouse-leave events
    (when (and (slot-value w 'browse-mode)    ; in browser mode and
               item)                          ; when mouse is over an item
      (cond ((eq last-under-mouse item)
             (mouse-within item where))
            (t (if last-under-mouse
                 (mouse-leave last-under-mouse where))
               (setf (slot-value w 'item-last-under-mouse) item)
               (mouse-enter item where)))
      ;; Handle idle event for window
      (idle w)))
  (call-next-method))

Here, we bound the variable w to the instance of the 
draw-dialog window to which the message was sent. 
The let* statement creates the following local 
variables:

where	The current location of the mouse as a
	point.

item	The item (either the window or an item
	in the window) under the mouse.

last-under-mouse
	The item that was under the mouse
	last time we checked.

The MCL method view-mouse-position applied to our 
window returns to us the position of the mouse in 
local window coordinates. The MCL method find-
view-containing-point returns the most specific 
view under the mouse: either an item in the window, or 
the window itself if the mouse isnÕt over an item. 
Since we plan to treat our draw items as views (that 
is, as subviews of the window they are contained in), 
we will for now let find-view-containing-point do 
the work for us. Finally, the item-last-under-mouse 
slot is extracted here for performance since weÕll 
make multiple references to it.

The cond statement figures out which of and to whom 
the following messages should be sent: mouse-enter, 
mouse-within, or mouse-leave. We check the value of 
the windowÕs slot browse-mode to see if the window 
is in browse or author mode. We only want to dispatch 
the events when in browse mode.

Note how the slot item-last-under-mouse is set in 
that conditional. The general expression for setting a 
slot value is:

(setf (slot-value <object> <slot-name>) <value>)

The setf function is a generalization of setq and can 
therefore be used in its place. setf enables you to 
make an expression evaluate to the value you want. 
More precisely, evaluating

(setf <list-or-symbol> <value>)

implies that evaluating <list-or-symbol> will return 
<value>. For symbols, this is always the case. For 
lists, this is only possible for certain system-defined 
functions, like slot-value. But you are free to define 
any function to behave this way: consult define-setf-
method in your friendliest Common Lisp book.

Next to last, we call the idle method in the window. We 
have defined this method above: it distributes the idle 
event to all items in it. Finally, we call call-next-
method, which is the way CLOS enables us to call the 
window-null-event-handler method that draw-
dialog would otherwise have inherited from the class 
window. If we wanted to override it completely, we 
would not have called call-next-method. The latter is 
similar to MacAppÕs inherit- or SmallTalkÕs CallSuper 
methods.

WeÕre only left with mouse-down, mouse-up, and 
key events to worry about. WeÕre lucky again, because 
MCL is always busy in the background dispatching 
calls to the MCL methods view-click-event-handler, 
window-mouse-up-event-handler, and view-key-
event-handler, which are called after the mouse goes 
down or up, or after a keystroke, respectively. These 
methods are a breeze to code:

(defmethod view-click-event-handler ((w draw-dialog) where)
  (let ((item (find-view-containing-point 
               w (point-h where) (point-v where))))
    (if (slot-value w 'browse-mode)
      (if item
        (mouse-down item where))
      (author-mode-click-handler item where))))

(defmethod window-mouse-up-event-handler ((w draw-dialog))
  (let* ((where (view-mouse-position w))
         (item (find-view-containing-point
                w (point-h where) (point-v where))))
    (if (and item (slot-value w 'browse-mode))
      (mouse-up item where)))
  (call-next-method))

(defmethod view-key-event-handler ((w draw-dialog) character)
  (let* ((where (view-mouse-position w))
         (item (find-view-containing-point
                w (point-h where) (point-v where))))
    (if (and item (slot-value w 'browse-mode))
      (key item character)))
  (call-next-method))

The mouse position is supplied by MCL in view-click-
event-handler, which is called when the mouse is 
pressed down; in the other cases we find it using 
view-mouse-position. For the view-key-event-
handler MCL passes the character for the key that 
was pressed. Again, we call call-next-method in case 
it does anything important. The method author-mode-
click-handler called by view-click-event-handler 
will take care of object selection, deselection, 
resizing, and dragging operations similarly to 
HyperCard. Note that this method is invoked on the 
item, which could be the window itself or one of the 
graphic items in it. The latter version is described in 
the section ÒCreating Draw Items,Ó while the 
definition for the window version is:

(defmethod author-mode-click-handler ((w draw-dialog) where)
  (if (double-click-p)
    (author-mode-double-click-handler w where)
    (author-mode-single-click-handler w where)))

double-click-p is a MCL function that tells us 
whether we have a double click. The method author-
mode-double-click-handler (not shown in this 
article) will be responsible for presenting a dialog box 
with information about the window, while:

(defmethod author-mode-single-click-handler ((w draw-dialog) where)
  (when (eq w (find-view-containing-point
               w (point-h where) (point-v where))
            (deselect-items w)))

The latter method will deselect everything when the 
mouse is clicked over the window and there are no 
items under it. The method deselect-items is 
straightforward:

(defmethod deselect-items ((window draw-dialog))
  (dolist (item (slot-value window 'selections))
    (setf (slot-value item 'selected) nil)
    (view-draw-contents item))         ; Redraw item
  (setf (slot-value window 'selections) nil))

We cycle through each item that has been put into the 
selections slot of the window and turn off its 
selected flag (see the ÒCreating Draw ItemsÓ section), 
redraw the item, and then delete the selections slot.

As we define more functions and methods, our text 
windows are becoming rather large. Fortunately, we 
can go to the MCL Toolsmenu and choose List 
Definitions to get the window shown in Figure 3. Each 
line in this window shows a function, method, and 
other kinds of definitions found in the text window. By 
double-clicking on any item, you can scroll the text to 
the right item. It is possible to list things 
alphabetically or in the order they appear in the 
window. One of these windows can be simultaneously 
opened per opened text window.

Figure 3: [ See Figure 2 in develop, p98 ]

The "List Definitions" Window

We are done with the window class, except for a 
detail concerning find-view-containing-point. We 
would like it to take into account the back-to-front 
ordering of items in our window, but the view system 
will not necessarily be aware of it. Therefore, we will 
override this method by our own version, which will 
cycle through the items in the my-values slot of the 
draw-dialog class in the correct front-to-back order.
[ Don't look for a definition of find-view-containing-point,
  you won't find it. The built-in version is used. It works
  fine. ]

Creating Our Palette

Our palette will have two kinds of objects in it:

¥ Tools to select what should be done

¥ Objects that can be dragged into our windows

It seems that the draw-dialog class can do most of 
the work for us, so we will define a subclass of it 
called palette as follows:

(defclass palette (draw-dialog)
  ((my-tools      :initarg :tools)
   (my-draw-items :initarg :draw-items))
  (:documentation "Palettes used in our application"))

The my-tools slot will contain all the items in the 
palette that can be viewed as tools, whereas the my-
draw-items slot will contain all items that can be 
dragged out of the palette into other windows. These 
slots will be initialized with instances of tools and 
graphic items that will be used in any one session of 
our application. Once a palette is created and 
initialized, a layout method (in CD ROM sources) will 
look at these slots to figure out how to lay out the 
itemsÑfor example, tools first and draggable items 
next. These are convenience slots, since (as weÕll see) 
graphic items themselves know whether they are tools 
or not.

We have to enforce the following differences between 
our palette and draw-dialog classes:

¥ Since tools are not accessible to users, a 
  convenient place to put the code that does 
  the toolÕs work when it is clicked is the 
  toolÕs mouse-down event handler.

¥ Items in our palette cannot be moved 
  within or resized in the palette.

¥ Tools cannot be dragged out of the palette.

First, we want to make sure that a paletteÕs tools get 
the mouse-down event whether or not we are in 
author mode. We can redefine view-click-event-
handler this way:

(defmethod view-click-event-handler ((palette palette) where)
  (let ((item (find-view-containing-point
               palette (point-h where) (point-v where))))
    (if (slot-value item 'tool)
      (mouse-down item where))   ; dispatch the mouse-down event
    (call-next-method)))         ; proceed with the usual behavior

If an item is selected and if it is a tool, we force the 
mouse-down and then call the draw-dialogÕs view-
click-event-handler method using call-next-method. 
The check (slot-value item 'tool) anticipates the 
definition for graphic items in the next section: the 
tool slot of an item tells it whether it is a tool or not.

Finally, since the resizing and dragging is done by the 
items themselves, items that know themselves to be 
in the palette should not allow themselves to be 
dragged or resized around a palette (but they should let 
themselves be dragged to other windows!). Similarly, 
items that are tools will know better than to drag 
themselves out of a palette! This (and more) weÕll 
discover in the following section.

Creating Draw Items

This is our last and most substantial section, for the 
graphic items have been delegated most of the work by 
the other classes. We will define one basic class of 
graphic items from which tools and other kinds of user 
interface objects will derive.

(defclass draw-item (dialog-item)
  ((rectangle :initarg :rect :initform nil)
   (tool :initarg :tool :initform nil)
   (selected :initform nil) 
   (name :initarg :name :initform ""))
  (:documentation "The user interface objects"))

We call our class draw-item. It will be a subclass of 
MCLÕs dialog-item class. The latter class supports a 
variety of specializations for typical Macintosh user 
interface items, like radio and round buttons, check 
boxes, and static text. Since we donÕt want to 
duplicate that functionality, we subclass from dialog-
item. Since dialog-item itself inherits from the class 
view (actually, from simple-viewÑbut letÕs not split 
hairs here), we get all the functionality we need 
without using multiple inheritance!

If you are using MCL 2.0 right now, you can verify this 
provenance by inspecting the class object for draw-
item using the inspector. find-class returns to you the 
class object, given a class name:

(inspect (find-class 'draw-item))

When the inspector window comes up, double-click on 
the line ccl::precedence-list: and you will get the 
window shown in Figure 4.

Figure 4: [ See Figure 5 in develop, p108 ]

The Class Precedence List for draw-item

You will notice that our draw-item not only inherits 
from simple-view, but also from stream. The latter 
class allows one to apply format, print, and other 
stream (input/output) methods to our items!

draw-itemÕs rectangle slot will be used to keep the 
bounds of the draw-item. These bounds will be the 
same as one would obtain by using the methods view-
position and view-size when applied to the item, but 
will be much more efficient than making two method 
calls. The tool slot tells us whether the item is a tool 
or not. The selected slot tells us whether the item is 
selected (note that this information is redundant since 
it is also maintained in the windowÕs selections slot). 
Finally, a name for the draw item.

Now, the business for draw-item to attend to, as we 
recall, is as follows:

¥ Implement the author-mode-click-
  handler method, which is responsible for 
  resizing and dragging an item.

¥ Implement a variety of useful subclasses 
  of draw-items, like editable text fields.

As we recall, the author-mode-click-handler method 
was called by the draw-dialog windowÕs view-click-
event-handler (see ÒCreating Our WindowÓ) when the 
mouse clicked on an item contained by the window 
when in author mode. Author mode allows us to resize 
and drag items in a window and, in our implementation, 
to even drag items between windows. LetÕs define this 
method:

(defmethod author-mode-single-click-handler
           ((item draw-item) mouse-loc)
  (if (mouse-moved (view-window item) mouse-loc)
    (if (resize? item mouse-loc)
      (resize item mouse-loc)
      (maybe-drag item mouse-loc)))
  (deselect-items (view-window item))
  (select-item item))

(defun mouse-moved (window mouse-loc)
  (loop
    (cond ((not (mouse-down-p))
           (return nil))
          ((neq (view-mouse-position window) mouse-loc)
           (return t)))))

The method view-window, when applied to a view, 
returns the window that contains it. First, we call 
mouse-moved to see if the mouse moved before it 
was released: if so, either a resize or drag is intended. 
We call resize? to test whether a resize is intended, 
and if so we call resize to do the resizing, or else we 
call maybe-drag. Why maybe-drag? Because we will 
not want to drag if the item is a tool. LetÕs look at 
resizing first.

resize? need only check that the mouse is touching the 
itemÕs direct manipulation Òhandles.Ó It must also 
deny resizing if the item is in a palette. For our 
purposes, the handles will be defined to be a 
rectangular frame around the bounds of the item, inset 
by a slop amount.

(defmethod resize? ((item draw-item) current-mouse-loc)
  (when (neq (type-of (view-window item)) 'PALETTE)    
    (rlet ((handles-rect :rect
                         :topleft (view-position item)
                         :bottomright (add-points
                                       (view-position item)
                                       (view-size item))))
      (inset-rect handles-rect *slop-in-pixels* *slop-in-pixels*)
      (not (point-in-rect-p handles-rect current-mouse-loc)))))

The when statement checks whether the item is in a 
palette. Note the use of the Common Lisp type-of 
function to determine the class of the window 
containing the item! rlet is a kind of let provided by 
MCL that allows one to bind a variable to a Macintosh 
record (that is, a pointer to a record in the Macintosh 
heap). The rlet is efficient because it allocates the 
record in the stack instead of calling the Memory 
Manager. We bind a rectangle record (the keyword :rect 
specifies this) to the variable handles-rect. The 
keywords :topleft and :bottomright allow us to supply 
the itemÕs position, which is deduced using view-
position and view-size methods. inset-rect is one of 
the QuickDraw utility functions available with MCL 
that enables us to inset the rectangle, and *slop-in-
pixels* is one of our globals that we bind to a number 
like 4. Finally, if the mouse location is not in the inset 
rectangle, then the user wants to resize.

The resize method illustrates the use of direct stack-
based toolbox calls: these are the functions whose 
names are prefixed by the underscore (_) character, 
like _InvertRect. In these calls, you can only pass a 
pointer, a single word, or a double word. You must take 
all the precautions you would if you were calling the 
traps from assembler. As a matter of fact, you are at 
assembler level when you make these calls: you can 
pass pointers or handles or a long number in a double 
word: youÕre the boss. 
[ A numbersign (#) is added so the trap (that's what toolbox
  calls are called) will be autoloaded on first use. ]

Another important feature of Common Lisp to observe 
in resize is the unwind-protect construct. This 
enables you to protect an expression (in this case, the 
long expression enclosed within the loop) in case 
thereÕs an error during its execution. The unwind-
protect guarantees that the expressions following the 
protected expression will be executed despite the 
error. We thus ensure that all the regions created for 
resize will be disposed of even if the routine crashes.

The drag method creates a region around the bounds of 
the thing to be dragged and calls the function drag-
inverted-region, which enables drags to occur 
between as well as within windows and returns an 
offset to where the item was dragged. If the 
destination window is the same as the window where 
the drag started and the window is a palette, we want 
to do nothing since we canÕt have a drag within a 
palette. Otherwise, we adjust the position of the item 
using set-view-position. If the move is to another 
window, then we use move-item-to-window. The 
latter will interpret a drag as a clone of the item if 
the drag started on a palette and ended in a draw-
dialog window, or else it will interpret it as a simple 
drag. move-item-to-window uses the MCL view 
methods add-subviews and remove-subviews to add 
and remove the items from the source and destination 
windows. If thereÕs a clone, the function clone-draw-
item is called and the item is added to the destination 
window. move-item-to-window ends by selecting the 
destination window.

Finally, we must define some useful draw-item 
subclasses. Since draw-item is a subclass of MCLÕs 
dialog-item class, any dialog-item object of MCL 
can be a draw-item. For example, to define a check box 
draw-item:

(defclass check-box (draw-item check-box-dialog-item)
  ())

This creates a class from which check boxes can be 
instantiated. Note the multiple inheritance: check-box 
inherits both from draw-item and from the predefined 
MCL check-box-dialog-item classes. check-box-
dialog-item (and the other MCL dialog items) have 
methods defined for drawing the radio buttons and 
performing the default actions associated with them. 
The drawing of the object is done by the method view-
draw-contents, which gets called whenever a draw 
item needs to be drawn by virtue of the fact that 
draw-item is a subclass of dialog-item. By this same 
device, we can define the default mouse-down event 
handlers for draw items to include the mouse-down 
behavior of the dialog items simply by:

(defmethod mouse-down ((item draw-item) where)
  (dialog-item-action item))

dialog-item-action is the MCL method that performs 
the action of a dialog item. The method for the dialog-
item class itself does nothing. The one for the check-
box-dialog-item does the checking or unchecking of 
the box, depending on its state. And so on.

If we want to define a brand new kind of item, we can 
do this as follows:

(defclass brand-new-kind-of-item (draw-item)
  ())

Suppose we want it to display as a red rectangle. Then 
we must define a view-draw-contents as follows:

(defmethod view-draw-contents ((item brand-new-kind-of-item))
  (with-port (wptr item)
    (with-fore-color *red-color*
      (#_FrameRect :ptr (slot-value item 'rectangle))))
  (call-next-method))

If we want it to beep when someone clicks on it, then 
we define something like:

(defmethod mouse-down ((item brand-new-kind-of-item) where)
  (ed-beep))

The complete source code provides a detailed view of 
how this works and looks together.


end of file All about the Mini-App.text
-----------------------------------------------
